﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace MessageMonitor
{
    /// <summary>
    /// Class of the screen which shows the messages.
    /// </summary>
    public partial class ScreenForm : Form
    {
        /// <summary>
        /// Two threads for getting data and sending data.
        /// </summary>
        Thread getDataThread, sendDataThread;
        Monitor monitor;
        IPAddress ip;
        int port;
        /// <summary>
        /// Message list.
        /// </summary>
        List<String> messageList = new List<string>(), allMessageList = new List<string>();
        /// <summary>
        /// Thread status.
        /// </summary>
        bool running = true, pause = false;

        /// <summary>
        /// Construction function of class ScreenForm.
        /// </summary>
        /// <param name="monitor">monitor</param>
        /// <param name="ip">IP Address of the server</param>
        /// <param name="port">Thr port of the server</param>
        public ScreenForm(Monitor monitor, IPAddress ip, int port)
        {
            this.monitor = monitor;
            this.ip = ip;
            this.port = port;
            InitializeComponent();
            this.Text = ip.ToString() + ":" + port;
        }
        /// <summary>
        /// Get messages from broadcast and show them on the screen.
        /// </summary>
        void GetData()
        {
            byte[] buf = new byte[10000];
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            IPEndPoint iep = new IPEndPoint(IPAddress.Any, monitor.BroadcastEndPoint.Port);
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            socket.Bind(iep);
            while (running)
            {
                if (pause)
                {
                    Thread.Sleep(100);
                    continue;
                }
                try
                {
                    string data = Encoding.ASCII.GetString(buf, 0, socket.Receive(buf));
                    string[] spi = data.Split(new char[]{'#'}, 3);
                    data = spi[2];
                    if (spi[0] == this.ip.ToString() && spi[1] == this.port.ToString())
                    {
                        this.allMessageList.Add(DateTime.Now.ToString("[yy/MM/dd HH:mm:ss] ") + data);
                        if (Valid(data))
                        {
                            this.screenBox.AppendText(DateTime.Now.ToString("[yy/MM/dd HH:mm:ss] "));
                            this.screenBox.AppendText(data + '\n');
                            this.messageList.Add(DateTime.Now.ToString("[yy/MM/dd HH:mm:ss] ") + data);
                        }
                        else if (monitor.ShowFilteredMessage)
                        {
                            this.screenBox.AppendText(DateTime.Now.ToString("[yy/MM/dd HH:mm:ss] "));
                            int len = this.screenBox.Text.Length;
                            this.screenBox.AppendText(data + '\n');
                            monitor.AC.Highlight(len, data, this.screenBox);
                            this.messageList.Add(DateTime.Now.ToString("[yy/MM/dd HH:mm:ss] ") + data);
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
            }
            socket.Close();
        }
        /// <summary>
        /// Get the messages from the server and broadcast them.
        /// </summary>
        void SendData()
        {
            byte[] buf = new byte[10000];
            while (running)
            {
                Socket tcpClient = null;
                try
                {
                    tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    tcpClient.Connect(ip, port);
                }
                catch
                {
                    Thread.Sleep(1000);
                    continue;
                }
                Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
                socket.Connect(monitor.BroadcastEndPoint);
                while (running)
                {
                    try
                    {
                        string data = Encoding.ASCII.GetString(buf, 0, tcpClient.Receive(buf));
                        if (data.Length == 0)
                        {
                            break;
                        }
                        Console.WriteLine("data:" + data);
                        socket.Send(Encoding.ASCII.GetBytes(this.ip.ToString() + '#' + port.ToString() + '#' + data));
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.ToString());
                        break;
                    }
                }
                tcpClient.Close();
                socket.Close();
            }
        }

        /// <summary>
        /// Start working with two threads.
        /// The first thread trys to fetch data from server and broadcast it.
        /// The second thread trys to get data from broadcast and show it.
        /// </summary>
        public void Start()
        {
            this.getDataThread = new Thread(new ThreadStart(GetData));
            this.getDataThread.IsBackground = true;
            this.getDataThread.Start();
            this.sendDataThread = new Thread(new ThreadStart(SendData));
            this.sendDataThread.IsBackground = true;
            this.sendDataThread.Start();
        }

        /// <summary>
        /// Check the message if it is valid.
        /// </summary>
        /// <param name="s">the message</param>
        /// <returns>Returns true if it is valid, otherwise return false.</returns>
        private bool Valid(String s)
        {
            string rule = this.monitor.Rule;
            if (rule == null)
            {
                return true;
            }
            HashSet<String> which = monitor.AC.GetWhich(s);
            Stack<bool> sta = new Stack<bool>();
            Stack<int> opr = new Stack<int>();
            try
            {
                for (int i = 0; i < rule.Length; ++i)
                {
                    if (rule[i] == '"')
                    {
                        int j = i + 1;
                        while (j < rule.Length && rule[j] != '"')
                        {
                            ++j;
                        }
                        sta.Push(which.Contains(rule.Substring(i + 1, j - i - 1)));
                        i = j;

                        while (opr.Count > 0 && opr.Peek() == 1)
                        {
                            sta.Push(sta.Pop() & sta.Pop());
                            opr.Pop();
                        }
                    }
                    else if (i + 3 <= rule.Length && rule.Substring(i, 3) == "and")
                    {
                        i += 2;
                        opr.Push(1);
                    }
                    else if (i + 2 <= rule.Length && rule.Substring(i, 2) == "or")
                    {
                        i += 1;
                        opr.Push(0);
                    }
                    else if (rule[i] == '(')
                    {
                        opr.Push(-1);
                    }
                    else if (rule[i] == ')')
                    {
                        while (opr.Peek() != -1)
                        {
                            sta.Push(sta.Pop() | sta.Pop());
                            opr.Pop();
                        }
                        opr.Pop();
                        while (opr.Count > 0 && opr.Peek() == 1)
                        {
                            sta.Push(sta.Pop() & sta.Pop());
                            opr.Pop();
                        }
                    }
                }
                while (opr.Count > 0)
                {
                    sta.Push(sta.Pop() | sta.Pop());
                    opr.Pop();
                }
                return sta.Count == 0 || !sta.Pop();
            }
            catch
            {
                MessageBox.Show("Please check the rules");
                rule = null;
            }
            return true;
        }
        /// <summary>
        /// Save the messages to a file.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void fileButton_Click(object sender, EventArgs e)
        {
            try
            {
                var saveFileDialog = new SaveFileDialog();
                saveFileDialog.ShowDialog();
                StreamWriter filestream = new StreamWriter(saveFileDialog.OpenFile());
                int limit = int.Parse(this.limitBox.Text);
                for (int i = limit == 0 || messageList.Count < limit ? 0 : messageList.Count - limit; i < messageList.Count; ++i)
                {
                    filestream.WriteLine(messageList[i]);
                }
                filestream.Close();
                MessageBox.Show("File saved succefully.");
            }
            catch
            {
                MessageBox.Show("Save file failed, please check the limit and reselect the file.");
            }
        }
        /// <summary>
        /// Pause or resume showing the message.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pauseButton_Click(object sender, EventArgs e)
        {
            if (pause && this.pauseButton.Text == "Resume")
            {
                this.pauseButton.Text = "Pause";
                pause = false;
            }
            else if (pause == false && this.pauseButton.Text == "Pause")
            {
                this.pauseButton.Text = "Resume";
                pause = true;
            }
        }
        /// <summary>
        /// Refresh all the messages.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void refreshButton_Click(object sender, EventArgs e)
        {
            this.screenBox.Text = "";
            this.messageList.Clear();
            foreach (var s in this.allMessageList)
            {
                string data = s.Substring(20);
                if (Valid(data))
                {
                    this.screenBox.AppendText(s + '\n');
                    this.messageList.Add(s);
                }
                else if (monitor.ShowFilteredMessage)
                {
                    int len = this.screenBox.Text.Length;
                    this.screenBox.AppendText(s + '\n');
                    monitor.AC.Highlight(len + 20, data, this.screenBox);
                    this.messageList.Add(s);
                }
            }
        }

    }
}
